package com.jzo2o.orders.manager.service.impl;

import cn.hutool.core.bean.BeanUtil;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.jzo2o.api.customer.dto.response.AddressBookResDTO;
import com.jzo2o.api.foundations.dto.response.ServeAggregationResDTO;
import com.jzo2o.api.market.dto.request.CouponUseReqDTO;
import com.jzo2o.api.market.dto.response.AvailableCouponsResDTO;
import com.jzo2o.api.market.dto.response.CouponUseResDTO;
import com.jzo2o.api.orders.dto.response.OrderResDTO;
import com.jzo2o.api.trade.NativePayApi;
import com.jzo2o.api.trade.TradingApi;
import com.jzo2o.api.trade.dto.request.NativePayReqDTO;
import com.jzo2o.api.trade.dto.response.NativePayResDTO;
import com.jzo2o.api.trade.dto.response.TradingResDTO;
import com.jzo2o.api.trade.enums.PayChannelEnum;
import com.jzo2o.api.trade.enums.TradingStateEnum;
import com.jzo2o.common.expcetions.BadRequestException;
import com.jzo2o.common.expcetions.CommonException;
import com.jzo2o.common.expcetions.DBException;
import com.jzo2o.common.model.msg.TradeStatusMsg;
import com.jzo2o.common.utils.DateUtils;
import com.jzo2o.common.utils.NumberUtils;
import com.jzo2o.common.utils.ObjectUtils;
import com.jzo2o.mvc.utils.UserContext;
import com.jzo2o.orders.base.config.OrderStateMachine;
import com.jzo2o.orders.base.constants.RedisConstants;
import com.jzo2o.orders.base.enums.OrderPayStatusEnum;
import com.jzo2o.orders.base.enums.OrderStatusChangeEventEnum;
import com.jzo2o.orders.base.enums.OrderStatusEnum;
import com.jzo2o.orders.base.mapper.OrdersMapper;
import com.jzo2o.orders.base.model.domain.Orders;
import com.jzo2o.orders.base.model.dto.OrderSnapshotDTO;
import com.jzo2o.orders.manager.model.dto.request.OrdersPayReqDTO;
import com.jzo2o.orders.manager.model.dto.request.PlaceOrderReqDTO;
import com.jzo2o.orders.manager.model.dto.response.OrdersPayResDTO;
import com.jzo2o.orders.manager.model.dto.response.PlaceOrderResDTO;
import com.jzo2o.orders.manager.porperties.TradeProperties;
import com.jzo2o.orders.manager.service.IOrdersCreateService;
import com.jzo2o.orders.manager.service.client.CustomerClient;
import com.jzo2o.orders.manager.service.client.MarketClient;
import com.jzo2o.orders.manager.service.client.ServeClient;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.weaver.ast.Or;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;

/**
 * <p>
 * 下单服务类
 * </p>
 *
 * @author itcast
 * @since 2023-07-10
 */
@Slf4j
@Service
public class OrdersCreateServiceImpl extends ServiceImpl<OrdersMapper, Orders> implements IOrdersCreateService {

    @Resource
    private CustomerClient customerClient;
    @Resource
    private ServeClient serveClient;
    @Resource
    private RedisTemplate<String,Long> redisTemplate;
    @Resource
    private IOrdersCreateService currentProxy;
    @Autowired
    private TradeProperties tradeProperties;
    @Resource
    private NativePayApi nativePayApi;
    @Resource
    private TradingApi tradingApi;
    @Resource
    private OrderStateMachine orderStateMachine;
    @Autowired
    private MarketClient marketClient;

    @Override
    //placeOrder方法内需要远程调用查询地址薄和服务信息，如果网络传输时间较长就会导致长事务
    //只针对下单(保存到订单)这一步操作加事务，而不必对整个placeOrder方法添加事务
    //@Transactional(rollbackFor = Exception.class)
    public PlaceOrderResDTO placeOrder(PlaceOrderReqDTO dto) {
        //1. 获取地址薄信息：远程调用customer服务，传参地址薄id，获取地址薄信息
        AddressBookResDTO addressBook = customerClient.findAddressBookById(dto.getAddressBookId());
        if (addressBook==null) {
            throw new BadRequestException("预约地址异常");
        }
        //2. 获取服务信息：远程调用foundations服务，传参区域服务id，获取服务项和服务信息
        ServeAggregationResDTO serve = serveClient.findById(dto.getServeId());
        if (serve==null) {
            throw new BadRequestException("服务不可用");
        }
        //3. 生成订单号：生成规则 yyMMdd + 13位序列号
        Long orderId = generateOrderId();
        //4. 计算价格

        //5. 组装订单信息，存入订单表
        Orders order = new Orders();
        order.setId(orderId);
        order.setUserId(UserContext.currentUserId());
        order.setServeTypeId(serve.getServeTypeId());
        order.setServeTypeName(serve.getServeTypeName());
        order.setServeItemId(serve.getServeItemId());
        order.setServeItemName(serve.getServeItemName());
        order.setServeItemImg(serve.getServeItemImg());
        order.setUnit(serve.getUnit());
        order.setServeId(serve.getId());
        order.setOrdersStatus(OrderStatusEnum.NO_PAY.getStatus());
        order.setPayStatus(OrderPayStatusEnum.NO_PAY.getStatus());
        order.setPrice(serve.getPrice());
        //校验万一没传数量, 就给它一个默认值
        order.setPurNum(NumberUtils.null2Default(dto.getPurNum(),1));
        //计算总价 BigDecimal类型的必须与BigDecimal类型运算
        order.setTotalAmount(serve.getPrice().multiply(new BigDecimal(dto.getPurNum())));
        //还没实现优惠券功能, 先写死为0
        order.setDiscountAmount(BigDecimal.ZERO);
        //实付金额: 总金额 - 优惠金额
        order.setRealPayAmount(NumberUtils.sub(order.getTotalAmount(),order.getDiscountAmount()));
        order.setCityCode(serve.getCityCode());
        order.setServeAddress(addressBook.getProvince() + addressBook.getCity() + addressBook.getCounty() + addressBook.getAddress());
        order.setContactsPhone(addressBook.getPhone());
        order.setContactsName(addressBook.getName());
        order.setServeStartTime(dto.getServeStartTime());
        order.setLon(addressBook.getLon());
        order.setLat(addressBook.getLat());
        //排序字段，serve_start_time秒级时间戳+订单id后六位'  DateUtils.toEpochMilli() 是将方法转化为毫秒值的时间戳
        order.setSortBy(DateUtils.toEpochMilli(order.getServeStartTime()) + orderId % 1000000);

        //新增订单
        //save(order);
        //this.add(order);   这里的add()方法是非事务方法, 没有经过代理对象 事务会失效
        //通过注入进来的ordersCreateService对象调用add方法 这里的ordersCreateService对象是代理对象, 不是它自己,不会导致循环依赖
        if (dto.getCouponId() == null) {
            //没有使用优惠券，执行原本的逻辑，使用本地事务，不需要远程调用核销优惠券
            currentProxy.add(order);
        }else {
            //用户选择了优惠券，需要远程调用核销优惠券，并使用分布式事务
            currentProxy.addWithCoupon(order,dto.getCouponId());
        }

        //int a = 1 / 0;  这里有异常也还是能下单成功的??
        //构建返回值
        PlaceOrderResDTO placeOrderResDTO = new PlaceOrderResDTO();
        placeOrderResDTO.setId(orderId);
        return placeOrderResDTO;

    }

    @Override
    @Transactional
    public void addWithCoupon(Orders order, Long couponId) {
        //1. 远程调用market服务，核销优惠券，获取实际优惠金额
        CouponUseReqDTO dto = new CouponUseReqDTO();
        dto.setId(couponId);
        dto.setOrdersId(order.getId());
        dto.setTotalAmount(order.getTotalAmount());
        CouponUseResDTO couponUseResDTO = marketClient.use(dto);

        order.setDiscountAmount(couponUseResDTO.getDiscountAmount());
        //2. 计算订单的实际应付金额
        order.setRealPayAmount(order.getTotalAmount().subtract(order.getDiscountAmount()));

        //3. 保存订单
        add(order);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(Orders order) {
        boolean saved = save(order);
        if (!saved) {
            throw new DBException("下单失败");
        }
        //创建订单快照, 启动状态机
        OrderSnapshotDTO snapshot = BeanUtil.toBean(getById(order.getId()),OrderSnapshotDTO.class);
        //order.getUserId()根据userId去分库 分库规则jzo2o-orders-${user_id % 3}
        orderStateMachine.start(order.getUserId(),order.getId().toString(),snapshot);
    }

    /**
     * 生成订单id 格式：{yyMMdd}{13位id}
     */
    private Long generateOrderId() {
        //通过Redis自增序列得到序号
        /**
         * 线程安全：
         * Redis 的 INCRBY 命令是原子操作，因此即使在高并发环境下，多个客户端同时调用 increment 方法也不会导致 ID 冲突。
         * 每个请求都会依次获得递增的唯一 ID。
         *
         *初始值：
         * 如果 Redis 中的键不存在，increment 方法会自动初始化该键的值为 0，然后根据 delta 参数进行自增。因此，第一次调用时，生成的 ID 将是 1
         *
         * redis客户端操作:  INCR key
         */
        Long id = redisTemplate.opsForValue().increment(RedisConstants.Lock.ORDERS_SHARD_KEY_ID_GENERATOR, 1);
        //生成订单号 2位年+2位月+2位日+13位序号  20241221 + 0000000000001
        return DateUtils.getFormatDate(LocalDateTime.now(),"yyMMdd") * 10000000000000L + id;
    }


    @Override
    public OrdersPayResDTO pay(Long orderId, OrdersPayReqDTO dto) {
        //1. 查询订单信息, 如果不存在, 抛异常
        Orders orders = getById(orderId);
        if (orders==null) {
            throw new CommonException("订单不存在");
        }
        //2. 如果订单是已支付成功状态、且交易单号不为空， 说明确实是已支付状态， 直接返回结果
        if (orders.getOrdersStatus() == OrderPayStatusEnum.PAY_SUCCESS.getStatus()
                && orders.getTradingOrderNo() != null) {
            OrdersPayResDTO res = BeanUtil.toBean(orders, OrdersPayResDTO.class);
            res.setProductOrderNo(orderId);
            return res;
        }
        //3. 远程调用trade支付服务申请支付， 获取二维码
        NativePayReqDTO nativePayReqDTO = new NativePayReqDTO();
        nativePayReqDTO.setProductOrderNo(orders.getId());
        //是否切换了支付渠道：如果提交的参数渠道  和 order里上次的渠道不同，就说明要切换支付渠道了
        nativePayReqDTO.setChangeChannel(!ObjectUtils.equal(orders.getTradingChannel(),dto.getTradingChannel()));
        nativePayReqDTO.setTradingAmount(orders.getRealPayAmount());
        nativePayReqDTO.setTradingChannel(dto.getTradingChannel());
        nativePayReqDTO.setEnterpriseId(ObjectUtils.equal(PayChannelEnum.ALI_PAY,dto.getTradingChannel())?
                tradeProperties.getAliEnterpriseId():tradeProperties.getWechatEnterpriseId());
        //设置productAppId:我们的预约订单，使用一个固定值jzo2o.orders
        nativePayReqDTO.setProductAppId("jzo2o.orders");
        nativePayReqDTO.setMemo(orders.getServeItemName());
        //远程调用申请支付, 得到结果
        NativePayResDTO downLineTrading = nativePayApi.createDownLineTrading(nativePayReqDTO);
        if (downLineTrading == null) {
            throw new CommonException("订单" + orderId + "申请支付失败");
        }
        //4. 把结果里支付渠道和交易单号更新到orders表里
        boolean updated = lambdaUpdate()
                .eq(Orders::getId, orderId)
                .set(Orders::getTradingChannel, downLineTrading.getTradingChannel())
                .set(Orders::getTradingOrderNo, downLineTrading.getTradingOrderNo())
                .update();
        if (!updated) {
            throw new CommonException("订单" + orderId + "更新交易单号失败");
        }
        //5. 构建返回结果
        OrdersPayResDTO ordersPayResDTO = new OrdersPayResDTO();
        ordersPayResDTO.setTradingOrderNo(downLineTrading.getTradingOrderNo());
        ordersPayResDTO.setTradingChannel(downLineTrading.getTradingChannel());
        ordersPayResDTO.setProductOrderNo(orderId);
        ordersPayResDTO.setQrCode(downLineTrading.getQrCode());
        return ordersPayResDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void paySuccess(TradeStatusMsg tradeStatusMsg) {
        /*boolean updated = lambdaUpdate()
                .eq(Orders::getId, tradeStatusMsg.getProductOrderNo())
                .set(Orders::getOrdersStatus, OrderStatusEnum.DISPATCHING.getStatus())
                .set(Orders::getPayStatus, OrderPayStatusEnum.PAY_SUCCESS.getStatus())
                .set(Orders::getPayTime, LocalDateTime.now())
                .set(Orders::getTradingOrderNo, tradeStatusMsg.getTradingOrderNo())
                .set(Orders::getTransactionId, tradeStatusMsg.getTransactionId())
                .update();
        if (!updated) {
            throw new DBException("更新订单" + tradeStatusMsg.getProductOrderNo() + "支付状态异常");
        }*/

        //支付成功后, 期望让状态机也存储支付相关的一些状态
        OrderSnapshotDTO snapshot = new OrderSnapshotDTO();
        snapshot.setTradingChannel(tradeStatusMsg.getTradingChannel());
        snapshot.setThirdOrderId(tradeStatusMsg.getTransactionId());
        snapshot.setTradingOrderNo(tradeStatusMsg.getTradingOrderNo());
        snapshot.setPayTime(LocalDateTime.now());
        snapshot.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
        //获取用户id,设置分片的userId
        Orders orders = getById(tradeStatusMsg.getProductOrderNo());
        orderStateMachine.changeStatus(orders.getUserId(),tradeStatusMsg.getProductOrderNo().toString(),
                OrderStatusChangeEventEnum.PAYED,snapshot);
    }

    @Override
    public OrdersPayResDTO payResult(Long orderId) {
        //1.查订单
        Orders order = getById(orderId);
        if (order == null) {
            throw new BadRequestException("订单不存在");
        }
        //2. 如果订单状态不是已支付，就需要远程调用trade服务，查询一下最新的支付状态。如果是已支付，要更新订单状态
        if (ObjectUtils.equals(order.getOrdersStatus(), OrderStatusEnum.NO_PAY.getStatus())) {
            //远程调用trade服务查询最新支付状态
            TradingResDTO tradeResult = tradingApi.findTradResultByTradingOrderNo(order.getTradingOrderNo());
            if (ObjectUtils.equals(tradeResult.getTradingState(), TradingStateEnum.YJS)) {
                //如果是已支付, 需要更新订单状态
                TradeStatusMsg tradeStatusMsg = new TradeStatusMsg();
                tradeStatusMsg.setTradingOrderNo(tradeResult.getTradingOrderNo());
                tradeStatusMsg.setTransactionId(tradeResult.getTransactionId());
                tradeStatusMsg.setProductOrderNo(orderId);
                //事务会失效, 调用代理对象
                currentProxy.paySuccess(tradeStatusMsg);

                //构造返回值
                OrdersPayResDTO res = new OrdersPayResDTO();
                res.setTradingChannel(tradeResult.getTradingChannel());
                res.setTradingOrderNo(tradeResult.getTradingOrderNo());
                res.setProductOrderNo(orderId);
                res.setPayStatus(OrderPayStatusEnum.PAY_SUCCESS.getStatus());
                return res;
            }
        }

        //3. 返回结果
        OrdersPayResDTO res = new OrdersPayResDTO();
        res.setTradingChannel(order.getTradingChannel());
        res.setTradingOrderNo(order.getTradingOrderNo());
        res.setProductOrderNo(orderId);
        res.setPayStatus(order.getPayStatus());
        return res;
    }

    /**
     * 查询可用优惠券列表
     * @param serveId
     * @param purNum
     * @return
     */
    @Override
    public List<AvailableCouponsResDTO> getAvailableCoupons(Long serveId, Integer purNum) {
        //1. 远程调用foundations服务, 查询serveId对应的服务信息
        ServeAggregationResDTO serve = serveClient.findById(serveId);
        if (ObjectUtils.isEmpty(serve)) {
            throw new BadRequestException("服务不存在");
        }
        //2. 计算订单总金额
        BigDecimal totalAmount = serve.getPrice().multiply(new BigDecimal(purNum));

        //远程调用market服务, 查询可用的优惠券列表
        return marketClient.getAvailableCoupon(totalAmount);
    }
}
